package net.paoding.rose.impl.scanning;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import net.paoding.rose.RoseConstants;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.VFS;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.web.context.WebApplicationContext;

/**
 * 
 * @author zhiliang.wang [qieqie.wang@gmail.com]
 * 
 */
public abstract class AbstractScanner implements ApplicationContextAware, InitializingBean {
	protected Log logger = LogFactory.getLog(getClass());
	protected ApplicationContext applicationContext;
	protected ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	public void setClassLoader(ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		scan();
	}

	public void scan() throws IOException, FileSystemException {
		ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver(
				classLoader);

		// classes folders
		Resource[] classResources = findClassResources(resourceLoader);
		// jar folders
		Resource[] jarResources = findJarResources(resourceLoader);
		//
		FileSystemManager fsManager = VFS.getManager();
		ArrayList<FileObject> rootObjects = new ArrayList<FileObject>(classResources.length
				+ jarResources.length);
		for (Resource resource : classResources) {
			FileObject rootObject = fsManager.resolveFile(resource.getFile().getPath());
			rootObjects.add(rootObject);
		}
		for (Resource resource : jarResources) {
			FileObject rootObject = fsManager.resolveFile("jar:file:///"
					+ resource.getFile().getPath().replace('\\', '/'));
			rootObjects.add(rootObject);
		}
		// clean before scanning
		beforeScanning();
		//
		doScanning(rootObjects);
		//
		afterScanning();
		//
		for (FileObject fileObject : rootObjects) {
			fsManager.closeFileSystem(fileObject.getFileSystem());
		}
	}

	protected void beforeScanning() {
		clear();
	}

	protected void clear() {
	}

	protected void doScanning(List<FileObject> fileObjects) {
		for (FileObject fileObject : fileObjects) {
			scanImpl(fileObject, fileObject);
		}
	}

	protected void scanImpl(FileObject rootObject, FileObject fileObject) {
		try {
			if (!fileObject.getType().equals(FileType.FOLDER)) {
				logger.warn("fileObject shoud be a folder", new IllegalArgumentException());
				return;
			}
			if (isFolderMatched(fileObject)) {
				handleWithFolder(rootObject, fileObject, fileObject);
			} else {
				FileObject[] children = fileObject.getChildren();
				for (FileObject child : children) {
					if (child.getType().equals(FileType.FOLDER)) {
						scanImpl(rootObject, child);
					}
				}
			}
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	protected abstract boolean isFolderMatched(FileObject folderObject);

	protected abstract void handleWithFolder(FileObject rootObject, FileObject matchedRootFolder,
			FileObject thisFolder) throws IOException;

	protected void afterScanning() {
	}

	// -------------------------------------------------------------

	/**
	 * 将要被扫描的普通类地址(比如WEB-INF/classes或target/classes之类的地址)
	 * 
	 * @param resourceLoader
	 * @return
	 * @throws IOException
	 */
	protected Resource[] findClassResources(ResourcePatternResolver resourceLoader)
			throws IOException {
		Resource[] classResources;
		if (applicationContext instanceof WebApplicationContext) {
			classResources = applicationContext.getResources("/WEB-INF/classes/");
		} else {
			classResources = resourceLoader.getResources("classpath:");
		}
		if (logger.isDebugEnabled()) {
			if (classResources.length == 0) {
				logger.debug("not found class folder");
			}
			else {
				for (Resource resource : classResources) {
					logger.debug("found class folder: " + resource.getURL());
				}
			}
		}
		return classResources;
	}

	/**
	 * 将要被扫描的jar资源
	 * 
	 * @param resourceLoader
	 * @return
	 * @throws IOException
	 */
	protected Resource[] findJarResources(ResourcePatternResolver resourceLoader)
			throws IOException {
		// 配置要扫描哪些jar?
		String[] jarNames = getJarNames();
		if (jarNames.length == 0) {
			logger.info("unusing jar scanning");
			return new Resource[0];
		}

		// jar可能存放在什么地方?
		LinkedList<String> jarFolders = getJarFolders(resourceLoader);

		// 用于将找到的jar作为一个Resource对象放到jarResourceList变量
		ArrayList<Resource> jarList = new ArrayList<Resource>();

		// 一个jar可能存于多个folder,jarResourceMap变量用于辅助排除重复的jar
		Map<String, Object> jarResourceMap = new HashMap<String, Object>();

		//
		for (String jarName : jarNames) {
			jarName = jarName.trim();
			if (StringUtils.isBlank(jarName)) {
				continue;
			}
			StringBuilder sb = new StringBuilder();
			for (String jarFolder : jarFolders) {
				String webappJarPath = jarFolder + jarName;
				Resource[] found = resourceLoader.getResources(webappJarPath);
				for (Resource resource : found) {
					if (!resource.exists() || jarResourceMap.containsKey(resource.getFilename())) {
						continue;
					}
					jarResourceMap.put(resource.getFilename(), resource);
					jarList.add(resource);
					if (logger.isInfoEnabled()) {
						sb.append(resource.getURL());
						sb.append(", ");
					}
				}
				if (sb.length() > 0) {
					logger.info("found jar resources: " + sb);
				}
			}
		}
		if (jarNames.length > 0 && jarList.size() == 0) {
			logger.info("not found jar resource for " + Arrays.toString(jarNames));
		}
		return jarList.toArray(new Resource[jarList.size()]);
	}

	/**
	 * 要扫描的jar的名字是?
	 * 
	 * @return
	 * @throws IOException
	 */
	private String[] getJarNames() throws IOException {
		String rosePropertiesPath = "rose.properties";
		if (applicationContext instanceof WebApplicationContext) {
			rosePropertiesPath = "/WEB-INF/rose.properties";
		}
		Resource rosePropertiesResource = applicationContext.getResource(rosePropertiesPath);
		if (rosePropertiesResource.exists()) {
			Properties roseProperties = new Properties();
			InputStream in = rosePropertiesResource.getInputStream();
			roseProperties.load(in);
			in.close();
			return roseProperties.getProperty(RoseConstants.CONF_WEBAPP_JAR_NAME,
					getDefaultJarName()).split(",|;");
		} else {
			return getDefaultJarName().split(",|;");
		}
	}

	/**
	 * jar都是放在什么目录地下的?
	 * 
	 * @param resourceLoader
	 * @return
	 * @throws IOException
	 */
	private LinkedList<String> getJarFolders(ResourcePatternResolver resourceLoader)
			throws IOException {
		LinkedList<String> jarFolders = new LinkedList<String>();
		if (applicationContext instanceof WebApplicationContext) {
			Resource resource = applicationContext.getResource("/WEB-INF/lib/");
			if (resource.exists()) {
				jarFolders.add(resource.getURL().toString());
			}
		}
		String[] testForJarResources = new String[] { "com", "org", "net" };
		for (String testResource : testForJarResources) {
			Enumeration<URL> found = resourceLoader.getClassLoader().getResources(testResource);
			while (found.hasMoreElements()) {
				URL urlObject = found.nextElement();
				if ("jar".equals(urlObject.getProtocol())) {
					String url = urlObject.toString().substring("jar:".length());
					int index = url.lastIndexOf("!/");
					index = url.lastIndexOf("/", index);
					String base = url.substring(0, index + 1);
					if (!jarFolders.contains(base)) {
						jarFolders.add(base);
					}
				}
			}
		}
		if (logger.isInfoEnabled()) {
			StringBuilder sb = new StringBuilder();
			for (String jarFolder : jarFolders) {
				sb.append(jarFolder).append(", ");
			}
			logger.info("found jar folders: " + sb);
		}
		return jarFolders;
	}

	/**
	 * 如果没有配置要扫描的jar，按此命名规则找jar
	 * 
	 * @return
	 */
	protected String getDefaultJarName() {
		return "*.web.jar, web.*.jar, xiaonei-*.jar";
	}

}
